/*
This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file is based on Keepassdroid, Copyright Brian Pellin.

  Keepass2Android is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  Keepass2Android is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with Keepass2Android.  If not, see <http://www.gnu.org/licenses/>.
  */

using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.Database;
using Android.Graphics;
using Android.Graphics.Drawables;
using Android.OS;
using Android.Preferences;
using Android.Runtime;
using Android.Text;
using Android.Views;
using Android.Views.InputMethods;
using Android.Widget;
using AndroidX.AppCompat.App;
using AndroidX.CoordinatorLayout.Widget;
using AndroidX.Core.Content;
using AndroidX.Core.View;
using AndroidX.DrawerLayout.Widget;
using Google.Android.Material.AppBar;
using Google.Android.Material.Dialog;
using Java.Lang;
using Java.Net;
using KeeChallenge;
using keepass2android;
using keepass2android.Io;
using keepass2android.Utils;
using Keepass2android.Pluginsdk;
using KeePassLib.Keys;
using KeePassLib.Serialization;
using OtpKeyProv;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Serialization;
using static Android.Locations.GpsStatus;
using AlertDialog = Android.App.AlertDialog;
using ClipboardManager = Android.Content.ClipboardManager;
using Enum = System.Enum;
using Exception = System.Exception;
using File = Java.IO.File;
using FileNotFoundException = Java.IO.FileNotFoundException;
using Object = Java.Lang.Object;
using Process = Android.OS.Process;
using String = System.String;
using Toolbar = AndroidX.AppCompat.Widget.Toolbar;

namespace keepass2android
{
  [Activity(Label = "@string/app_name",
      ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.Keyboard | ConfigChanges.KeyboardHidden,
      LaunchMode = LaunchMode.SingleInstance,
      WindowSoftInputMode = SoftInput.AdjustResize,
      Theme = "@style/Kp2aTheme_BlueNoActionBar")]
  public class PasswordActivity : LockingActivity, IBiometricAuthCallback
  {

    enum KeyProviders
    {
      KeyFile = 1,
      Otp = 2,
      OtpRecovery = 3,
      Challenge = 4,
      ChalRecovery = 5,
      ChallengeXC = 6, //KeepassXC compatible Challenge-Response
    }

    public const String KeyDefaultFilename = "defaultFileName";

    public const String KeyKeyfile = "keyFile";
    public const String KeyPassword = "password";
    public const String LaunchImmediately = "launchImmediately";


    private const string ShowpasswordKey = "ShowPassword";
    private const string KeyProviderIdOtp = "KP2A-OTP";
    private const string KeyProviderIdOtpRecovery = "KP2A-OTPSecret";
    private const string KeyProviderIdChallenge = "KP2A-Chal";
    private const string KeyProviderIdChallengeRecovery = "KP2A-ChalSecret";
    private const string KeyProviderIdChallengeXC = "KP2A-ChalXC";


    private const int RequestCodePrepareDbFile = 1000;
    private const int RequestCodePrepareOtpAuxFile = 1001;

    private const int RequestCodeSelectKeyfile = 1003;
    private const int RequestCodePrepareKeyFile = 1004;
    private const int RequestCodeSelectAuxFile = 1005;

    public const int ResultSelectOtherFile = (int)Result.FirstUser;


    private Task<MemoryStream> _loadDbFileTask;
    private bool _loadDbTaskOffline; //indicate if preloading was started with offline mode

    private IOConnectionInfo _ioConnection;
    private String _keyFile;
    bool _showPassword;

    private bool _killOnDestroy;
    private string _password = "";
    //OTPs which should be entered into the OTP fields as soon as these become visible
    private List<String> _pendingOtps = new List<string>();


    private HashSet<KeyProviders> KeyProviderTypes = new HashSet<KeyProviders>();

    private bool _rememberKeyfile;
    ISharedPreferences _prefs;

    private bool _starting;
    private bool _resumeCompleted;
    private OtpInfo _otpInfo;
    private IOConnectionInfo _otpAuxIoc;
    private ChallengeInfo _chalInfo;
    private byte[] _challengeSecret;
    private KeeChallengeProv _challengeProv;
    private readonly int[] _otpTextViewIds = new[] { Resource.Id.otp1, Resource.Id.otp2, Resource.Id.otp3, Resource.Id.otp4, Resource.Id.otp5, Resource.Id.otp6 };
    private const string OtpInfoKey = "OtpInfoKey";
    private const string EnteredOtpsKey = "EnteredOtpsKey";
    private const string PendingOtpsKey = "PendingOtpsKey";
    private const string PasswordKey = "PasswordKey";
    private const string KeyFileOrProviderKey = "KeyFileOrProviderKey";


    private bool _performingLoad;
    private bool _keepPasswordInOnResume;
    private DateTime _lastOnPauseTime = DateTime.MinValue;


    private ActionBarDrawerToggle mDrawerToggle;
    private DrawerLayout _drawerLayout;



    private string mDrawerTitle;
    private MeasuringLinearLayout.MeasureArgs _measureArgs;
    private ActivityDesign _activityDesign;
    private BiometricDecryption _biometricDec;
    private PasswordActivityBroadcastReceiver _intentReceiver;
    private int _appnameclickCount;


    public int InvalidCompositeKeyCount
    {
      get; set;
    }
    public int LoadingErrorCount
    {
      get; set;
    }


    private bool fingerprintInitialized;


    public bool UsedFingerprintUnlock { get; set; }
    readonly PasswordFont _passwordFont = new PasswordFont();
    private const string Kp2aKeyProviderStringPrefix = "_KP2A_KEYTYPES:";


    //can be set before launching the Activity. Will be used once to immediately open the database
    static CompositeKey compositeKeyForImmediateLoad = null;
    private bool _makeCurrent;



    public PasswordActivity(IntPtr javaReference, JniHandleOwnership transfer)
        : base(javaReference, transfer)
    {
      _activityDesign = new ActivityDesign(this);
    }

    public PasswordActivity()
    {
      _activityDesign = new ActivityDesign(this);
    }



    public static void Launch(Activity act, IOConnectionInfo ioc, CompositeKey compositeKey, ActivityLaunchMode launchMode, bool makeCurrent)
    {
      compositeKeyForImmediateLoad = compositeKey;
      Launch(act, ioc, launchMode, makeCurrent);
    }



    public static void Launch(Activity act, IOConnectionInfo ioc, ActivityLaunchMode launchMode, bool makeCurrent)
    {
      Intent i = new Intent(act, typeof(PasswordActivity));
      Util.PutIoConnectionToIntent(ioc, i);
      i.PutExtra("MakeCurrent", makeCurrent);

      launchMode.Launch(act, i);
    }

    public void LaunchNextActivity()
    {
      //StackBaseActivity will launch the next activity
      Intent data = new Intent();
      data.PutExtra("ioc", IOConnectionInfo.SerializeToString(_ioConnection));
      data.PutExtra("requiresSubsequentSync", _lastLoadOperation?.RequiresSubsequentSync == true);

      SetResult(Result.Ok, data);

      Finish();
    }

    protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
    {
      base.OnActivityResult(requestCode, resultCode, data);
      _keepPasswordInOnResume = true;
      Kp2aLog.Log("PasswordActivity.OnActivityResult " + resultCode + "/" + requestCode);

      switch (resultCode)
      {


        case Result.Ok:
          if (requestCode == RequestCodeSelectKeyfile)
          {
            IOConnectionInfo ioc = new IOConnectionInfo();
            Util.SetIoConnectionFromIntent(ioc, data);
            Kp2aLog.Log("Set keyfile after returning from RequestCodeSelectKeyfile");
            _keyFile = IOConnectionInfo.SerializeToString(ioc);
            UpdateKeyfileIocView();
          }
          break;
        case (Result)FileStorageResults.FileUsagePrepared:
          if (requestCode == RequestCodePrepareDbFile)
          {
            if (KeyProviderTypes.Contains(KeyProviders.KeyFile))
            {

              //if the user has not yet selected a keyfile, _keyFile is empty 
              if (string.IsNullOrEmpty(_keyFile) == false)
              {
                var iocKeyfile = IOConnectionInfo.UnserializeFromString(_keyFile);

                App.Kp2a.GetFileStorage(iocKeyfile)
                    .PrepareFileUsage(new FileStorageSetupInitiatorActivity(this, OnActivityResult, null), iocKeyfile,
                             RequestCodePrepareKeyFile, false);
              }

            }
            else
              PerformLoadDatabase();
          }
          if (requestCode == RequestCodePrepareKeyFile)
          {
            PerformLoadDatabase();
          }
          if (requestCode == RequestCodePrepareOtpAuxFile)
          {
            GetAuxFileLoader().LoadAuxFile(true);
          }
          break;
      }
      if (requestCode == RequestCodeSelectAuxFile && resultCode == Result.Ok)
      {
        IOConnectionInfo auxFileIoc = new IOConnectionInfo();
        Util.SetIoConnectionFromIntent(auxFileIoc, data);

        PreferenceManager.GetDefaultSharedPreferences(this).Edit()
                         .PutString("KP2A.PasswordAct.AuxFileIoc" + IOConnectionInfo.SerializeToString(_ioConnection),
                                    IOConnectionInfo.SerializeToString(auxFileIoc))
                         .Apply();

        GetAuxFileLoader().LoadAuxFile(false);
      }
      if (requestCode == RequestCodeChallengeYubikey)
      {
        if (CurrentlyWaitingKey != null)
        {
          //ActivityResult was handled in base class already
          return;
        }

        if (resultCode == Result.Ok)
        {

          try
          {
            byte[] challengeResponse = data.GetByteArrayExtra("response");
            _challengeProv = new KeeChallengeProv();
            _challengeSecret = _challengeProv.GetSecret(_chalInfo, challengeResponse);
            Array.Clear(challengeResponse, 0, challengeResponse.Length);
          }
          catch (Exception e)
          {
            Kp2aLog.Log(e.ToString());
            App.Kp2a.ShowMessage(this, "Error: " + Util.GetErrorMessage(e), MessageSeverity.Error);
            return;
          }

          UpdateOkButtonState();
          FindViewById(Resource.Id.otpInitView).Visibility = ViewStates.Gone;

          if (_challengeSecret != null)
          {
            new LoadingDialog<object, object, object>(this, true,
                //doInBackground
                delegate
                {
                  //save aux file
                  try
                  {
                    ChallengeInfo temp = _challengeProv.Encrypt(_challengeSecret);
                    if (!temp.Save(_otpAuxIoc))
                    {
                      App.Kp2a.ShowMessage(this, Resource.String.ErrorUpdatingChalAuxFile, MessageSeverity.Error);
                      return false;
                    }

                  }
                  catch (Exception e)
                  {
                    Kp2aLog.LogUnexpectedError(e);
                  }
                  return null;
                }
                , delegate
                {

                }).Execute();

          }
          else
          {
            App.Kp2a.ShowMessage(this, Resource.String.bad_resp, MessageSeverity.Error);
          }
        }
      }
    }



    private AuxFileLoader GetAuxFileLoader()
    {
      if (KeyProviderTypes.Contains(KeyProviders.Challenge))
      {
        return new ChallengeAuxFileLoader(this);
      }
      else
      {
        return new OtpAuxFileLoader(this);
      }
    }
    private void UpdateKeyfileIocView()
    {
      //store keyfile in the view so that we can show the selected keyfile again if the user switches to another key provider and back to key file
      FindViewById<TextView>(Resource.Id.label_keyfilename).Tag = _keyFile;
      if (string.IsNullOrEmpty(_keyFile))
      {
        FindViewById<TextView>(Resource.Id.filestorage_label).Visibility = ViewStates.Gone;
        FindViewById<ImageView>(Resource.Id.filestorage_logo).Visibility = ViewStates.Gone;
        FindViewById<TextView>(Resource.Id.label_keyfilename).Text = Resources.GetString(Resource.String.no_keyfile_selected);

        return;
      }
      var ioc = IOConnectionInfo.UnserializeFromString(_keyFile);
      string displayPath = App.Kp2a.GetFileStorage(ioc).GetDisplayName(ioc);
      int protocolSeparatorPos = displayPath.IndexOf("://", StringComparison.Ordinal);
      string protocolId = protocolSeparatorPos < 0 ?
          "file" : displayPath.Substring(0, protocolSeparatorPos);
      Drawable drawable = App.Kp2a.GetStorageIcon(protocolId);
      FindViewById<ImageView>(Resource.Id.filestorage_logo).SetImageDrawable(drawable);
      FindViewById<ImageView>(Resource.Id.filestorage_logo).Visibility = ViewStates.Visible;


      String title = App.Kp2a.GetStorageDisplayName(protocolId);
      FindViewById<TextView>(Resource.Id.filestorage_label).Text = title;
      FindViewById<TextView>(Resource.Id.filestorage_label).Visibility = ViewStates.Visible;

      FindViewById<TextView>(Resource.Id.label_keyfilename).Text = protocolSeparatorPos < 0 ?
          displayPath :
          displayPath.Substring(protocolSeparatorPos + 3);

    }



    private abstract class AuxFileLoader
    {
      protected readonly PasswordActivity Activity;

      protected AuxFileLoader(PasswordActivity activity)
      {
        Activity = activity;
      }

      public void LoadAuxFile(bool triggerSelectAuxManuallyOnFailure)
      {
        new LoadingDialog<object, object, object>(Activity, true,
                //doInBackground
                delegate
                {

                  try
                  {
                    var iocAux = GetDefaultAuxLocation();
                    LoadFile(iocAux);

                    if (Activity._chalInfo == null)
                    {
                      throw new Java.Lang.Exception("Failed to load challenge aux file");
                    }

                  }
                  catch (Exception e)
                  {
                    //this can happen e.g. if the file storage does not support GetParentPath
                    Kp2aLog.Log(e.ToString());
                    //retry with saved ioc
                    try
                    {
                      var savedManualIoc = IOConnectionInfo.UnserializeFromString(
                                  PreferenceManager.GetDefaultSharedPreferences(Activity).GetString(
                                      "KP2A.PasswordAct.AuxFileIoc" + IOConnectionInfo.SerializeToString(Activity._ioConnection), null));

                      LoadFile((savedManualIoc));
                    }
                    catch (Exception e2)
                    {
                      Kp2aLog.LogUnexpectedError(e2);
                    }

                  }
                  return null;
                }
                , delegate
                    {
                      if (!AuxDataLoaded)
                      {
                        if (triggerSelectAuxManuallyOnFailure)
                        {
                          Intent intent = new Intent(Activity, typeof(SelectStorageLocationActivity));
                          intent.PutExtra(FileStorageSelectionActivity.AllowThirdPartyAppGet, true);
                          intent.PutExtra(FileStorageSelectionActivity.AllowThirdPartyAppSend, false);
                          intent.PutExtra(FileStorageSetupDefs.ExtraIsForSave, false);
                          intent.PutExtra(SelectStorageLocationActivity.ExtraKeyWritableRequirements, (int)SelectStorageLocationActivityBase.WritableRequirements.WriteDemanded);
                          Activity.StartActivityForResult(intent, RequestCodeSelectAuxFile);
                        }
                        else
                        {
                          App.Kp2a.ShowMessage(Activity, GetErrorMessage(), MessageSeverity.Error);
                        }
                        return;

                      }
                      HandleSuccess();
                    }).Execute();

      }

      protected abstract bool AuxDataLoaded { get; }

      protected abstract void LoadFile(IOConnectionInfo iocAux);

      protected abstract void HandleSuccess();

      protected abstract string GetErrorMessage();

      protected abstract IOConnectionInfo GetDefaultAuxLocation();




    }

    private class OtpAuxFileLoader : AuxFileLoader
    {
      public OtpAuxFileLoader(PasswordActivity activity) : base(activity)
      {
      }

      protected override bool AuxDataLoaded
      {
        get { return Activity._otpInfo != null; }
      }

      protected override void LoadFile(IOConnectionInfo iocAux)
      {
        Activity._otpInfo = OtpInfo.Load(iocAux);
        Activity._otpAuxIoc = iocAux;
      }

      private static IOConnectionInfo GetAuxFileIoc(IOConnectionInfo databaseIoc)
      {
        IFileStorage fileStorage = App.Kp2a.GetOtpAuxFileStorage(databaseIoc);
        var parentPath = fileStorage.GetParentPath(databaseIoc);
        var filename = fileStorage.GetFilenameWithoutPathAndExt(databaseIoc) + OathHotpKeyProv.AuxFileExt;
        IOConnectionInfo iocAux = fileStorage.GetFilePath(parentPath, filename);
        return iocAux;
      }


      protected override void HandleSuccess()
      {
        IList<string> prefilledOtps = Activity._pendingOtps;
        Activity.ShowOtpEntry(prefilledOtps);
        Activity._pendingOtps.Clear();

      }

      protected override string GetErrorMessage()
      {
        return Activity.GetString(Resource.String.CouldntLoadOtpAuxFile) + " " +
            Activity.GetString(Resource.String.CouldntLoadOtpAuxFile_Hint);
      }

      protected override IOConnectionInfo GetDefaultAuxLocation()
      {
        return GetAuxFileIoc(Activity._ioConnection);
      }
    }

    private class ChallengeAuxFileLoader : AuxFileLoader
    {
      public ChallengeAuxFileLoader(PasswordActivity activity) : base(activity)
      {
      }

      protected override void HandleSuccess()
      {
        var chalIntent = Activity.TryGetYubichallengeIntentOrPrompt(Activity._chalInfo.Challenge, true);

        if (chalIntent != null)
        {
          Activity.StartActivityForResult(chalIntent, RequestCodeChallengeYubikey);
        }
      }

      protected override string GetErrorMessage()
      {
        return Activity.GetString(Resource.String.CouldntLoadChalAuxFile) +
               " " +
               Activity.GetString(
                   Resource.String.CouldntLoadChalAuxFile_Hint);
      }

      protected override bool AuxDataLoaded
      {
        get { return Activity._chalInfo != null; }
      }

      protected override void LoadFile(IOConnectionInfo iocAux)
      {
        Activity._chalInfo = ChallengeInfo.Load(iocAux);
        Activity._otpAuxIoc = iocAux;
      }


      protected override IOConnectionInfo GetDefaultAuxLocation()
      {
        IFileStorage fileStorage =
            App.Kp2a.GetOtpAuxFileStorage(Activity._ioConnection);
        IOConnectionInfo iocAux =
            fileStorage.GetFilePath(
                fileStorage.GetParentPath(Activity._ioConnection),
                fileStorage.GetFilenameWithoutPathAndExt(Activity._ioConnection) +
                ".xml");
        return iocAux;
      }
    }


    private void ShowOtpEntry(IList<string> prefilledOtps)
    {
      FindViewById(Resource.Id.otpInitView).Visibility = ViewStates.Gone;
      FindViewById(Resource.Id.otpEntry).Visibility = ViewStates.Visible;
      int c = 0;

      foreach (int otpId in _otpTextViewIds)
      {
        c++;
        var otpTextView = FindViewById<EditText>(otpId);
        if (c <= prefilledOtps.Count)
        {
          otpTextView.Text = prefilledOtps[c - 1];
        }
        else
        {
          otpTextView.Text = "";
        }
        otpTextView.Hint = GetString(Resource.String.otp_hint, new Object[] { c });
        otpTextView.SetFilters(new IInputFilter[] { new InputFilterLengthFilter((int)_otpInfo.OtpLength) });
        if (c > _otpInfo.OtpsRequired)
        {
          otpTextView.Visibility = ViewStates.Gone;
        }
        else
        {
          otpTextView.TextChanged += (sender, args) => { UpdateOkButtonState(); };
        }
      }
    }


    private void UncollapseToolbar()
    {
      AppBarLayout appbarLayout = FindViewById<AppBarLayout>(Resource.Id.appbar);
      var tmp = appbarLayout.LayoutParameters;
      CoordinatorLayout.LayoutParams p = tmp.JavaCast<CoordinatorLayout.LayoutParams>();
      var tmp2 = p.Behavior;
      var behavior = tmp2.JavaCast<AppBarLayout.Behavior>();
      if (behavior == null)
      {
        p.Behavior = behavior = new AppBarLayout.Behavior();
      }
      behavior.OnNestedFling(FindViewById<CoordinatorLayout>(Resource.Id.main_content), appbarLayout, null, 0, -10000, false);
    }

    private void CollapseToolbar()
    {
      AppBarLayout appbarLayout = FindViewById<AppBarLayout>(Resource.Id.appbar);
      ViewGroup.LayoutParams tmp = appbarLayout.LayoutParameters;
      CoordinatorLayout.LayoutParams p = tmp.JavaCast<CoordinatorLayout.LayoutParams>();
      var tmp2 = p.Behavior;
      var behavior = tmp2.JavaCast<AppBarLayout.Behavior>();
      if (behavior == null)
      {
        p.Behavior = behavior = new AppBarLayout.Behavior();
      }
      behavior.OnNestedFling(FindViewById<CoordinatorLayout>(Resource.Id.main_content), appbarLayout, null, 0, 200, true);
    }

    protected override void OnCreate(Bundle savedInstanceState)
    {
      _activityDesign.ApplyTheme();
      base.OnCreate(savedInstanceState);

      _intentReceiver = new PasswordActivityBroadcastReceiver(this);
      IntentFilter filter = new IntentFilter();
      filter.AddAction(Intent.ActionScreenOff);
      ContextCompat.RegisterReceiver(this, _intentReceiver, filter, (int)ReceiverFlags.Exported);


      //use FlagSecure to make sure the last (revealed) character of the master password is not visible in recent apps
      Util.MakeSecureDisplay(this);


      _prefs = PreferenceManager.GetDefaultSharedPreferences(this);
      _rememberKeyfile = _prefs.GetBoolean(GetString(Resource.String.keyfile_key), Resources.GetBoolean(Resource.Boolean.keyfile_default));

      SetContentView(Resource.Layout.password);

      InitializeToolbar();

      var passwordEdit = FindViewById<EditText>(Resource.Id.password_edit);
      passwordEdit.TextChanged +=
          (sender, args) =>
          {
            _password = passwordEdit.Text;
            UpdateOkButtonState();
          };
      passwordEdit.EditorAction += (sender, args) =>
      {
        if ((args.ActionId == ImeAction.Done) || ((args.ActionId == ImeAction.ImeNull) && (args.Event.Action == KeyEventActions.Down)))
          OnOk();
      };



      InitializeBottomBarButtons();

      InitializeNavDrawerButtons();

      InitializeTogglePasswordButton();
      InitializeKeyfileBrowseButton();
      InitializeOptionCheckboxes();
      FindViewById<EditText>(Resource.Id.pass_otpsecret).TextChanged += (sender, args) => UpdateOkButtonState();
      InitializeToolbarCollapsing();
      InitializeOtpSecretSpinner();

      //Intent-specific

      //fill _ioConnection, _keyFile, _password, _keepPasswordInOnResume

      Intent i = Intent;

      _ioConnection = new IOConnectionInfo();

      String action = i.Action;
      if ((action != null) && (action.Equals(Intents.StartWithOtp)))
      {
        Kp2aLog.Log("Launching with OTP");
        if (!GetIocFromOtpIntent(savedInstanceState, i)) return;
        _keepPasswordInOnResume = true;
      }
      else
      {
        GetIocFromLaunchIntent(i);
      }

      InitializeAfterSetIoc();


      RestoreState(savedInstanceState);

      if (i.GetBooleanExtra("launchImmediately", false))
      {
        App.Kp2a.GetFileStorage(_ioConnection)
               .PrepareFileUsage(new FileStorageSetupInitiatorActivity(this, OnActivityResult, null), _ioConnection,
                                 RequestCodePrepareDbFile, false);
      }


      mDrawerTitle = Title;


      var btn = FindViewById(Resource.Id.fingerprintbtn);
      btn.Click += (sender, args) =>
      {
        if (!string.IsNullOrEmpty((string)btn.Tag))
        {
          MaterialAlertDialogBuilder b = new MaterialAlertDialogBuilder(this);
          b.SetTitle(Resource.String.fingerprint_prefs);
          b.SetMessage(btn.Tag.ToString());
          b.SetPositiveButton(Android.Resource.String.Ok, (o, eventArgs) => ((Dialog)o).Dismiss());
          b.SetOnDismissListener(new Util.DismissListener(() => _biometricDec?.StartListening(this)));
          b.Show();
        }
        else _biometricDec?.StartListening(this);

      };


      if (App.Kp2a.TrySelectCurrentDb(_ioConnection))
      {
        //database already opened. return the ioc and we're good.
        LaunchNextActivity();
      }

      Util.SetNoPersonalizedLearning(FindViewById<EditText>(Resource.Id.password_edit));


    }

    private void InitializeAfterSetIoc()
    {
      App.Kp2a.RegisterOpenAttempt(_ioConnection);


      InitializeFilenameView();

      if (KeyProviderTypes.Contains(KeyProviders.KeyFile))
      {
        UpdateKeyfileIocView();
      }

      var passwordEdit = FindViewById<EditText>(Resource.Id.password_edit);
      passwordEdit.Text = _password;

      var passwordFont = Typeface.CreateFromAsset(Assets, "SourceCodePro-Regular.ttf");
      passwordEdit.Typeface = passwordFont;


      InitializePasswordModeSpinner();


      UpdateOkButtonState();
    }

    private void GetIocFromLaunchIntent(Intent i)
    {
      Kp2aLog.Log("GetIocFromLaunchIntent()");
      _makeCurrent = i.GetBooleanExtra("MakeCurrent", true);
      Util.SetIoConnectionFromIntent(_ioConnection, i);
      var keyFileFromIntent = i.GetStringExtra(KeyKeyfile);
      if (keyFileFromIntent != null)
      {
        Kp2aLog.Log("try get keyfile from intent");
        _keyFile = IOConnectionInfo.SerializeToString(IOConnectionInfo.FromPath(keyFileFromIntent));
        KeyProviderTypes.Clear();
        KeyProviderTypes.Add(KeyProviders.KeyFile);
        Kp2aLog.Log("try get keyfile from intent ok");
      }
      else
      {
        Kp2aLog.Log("no keyprovider specified");

        _keyFile = null;
        KeyProviderTypes.Clear();
      }

      _password = i.GetStringExtra(KeyPassword) ?? "";
      if (!KeyProviderTypes.Any())
      {
        SetKeyProviderFromString(LoadKeyProviderStringForIoc(_ioConnection.Path));
      }

      if ((!string.IsNullOrEmpty(_keyFile)) || (_password != ""))
      {
        _keepPasswordInOnResume = true;
      }
    }

    private void InitializeToolbarCollapsing()
    {
      var rootview = FindViewById<MeasuringLinearLayout>(Resource.Id.main_layout);
      rootview.ViewTreeObserver.GlobalLayout += (sender, args2) =>
      {
        Android.Util.Log.Debug("KP2A", "GlobalLayout");
        if (_measureArgs == null)
          return;
        Android.Util.Log.Debug("KP2A", "ActualHeight=" + _measureArgs.ActualHeight);
        Android.Util.Log.Debug("KP2A", "ProposedHeight=" + _measureArgs.ProposedHeight);
        if (_measureArgs.ActualHeight < _measureArgs.ProposedHeight)
          UncollapseToolbar();
        if (_measureArgs.ActualHeight > _measureArgs.ProposedHeight)
          CollapseToolbar();
      };
      rootview.MeasureEvent += (sender, args) => { _measureArgs = args; };
    }

    private string GetKeyProviderString()
    {
      if (!KeyProviderTypes.Any())
        return null;
      string result = Kp2aKeyProviderStringPrefix;
      foreach (KeyProviders type in KeyProviderTypes)
      {
        result += type.ToString();

        if (type == KeyProviders.KeyFile)
        {
          result += WebUtility.UrlEncode(_keyFile) + ";";
        }
      }
      return result;
    }

    private void SetKeyProviderFromString(string keyProviderString)
    {
      KeyProviderTypes.Clear();
      if (string.IsNullOrEmpty(keyProviderString))
      {
        Kp2aLog.Log("Reset keyfile");
        _keyFile = null;
        return;
      }

      if (keyProviderString.StartsWith(Kp2aKeyProviderStringPrefix))
      {
        keyProviderString = keyProviderString.Substring(Kp2aKeyProviderStringPrefix.Length);
        foreach (string type in keyProviderString.Split(';'))
        {
          Kp2aLog.Log("PasswordActivity: key file type " + type);
          if (!type.Trim().Any())
            continue;
          if (type.StartsWith(KeyProviders.KeyFile.ToString()))
          {
            _keyFile = WebUtility.UrlDecode(type.Substring(KeyProviders.KeyFile.ToString().Length));
            Kp2aLog.Log("Added key file of length " + _keyFile.Length);

            KeyProviderTypes.Add(KeyProviders.KeyFile);
            continue;
          }
          if (type.StartsWith(KeyProviders.ChallengeXC.ToString() + KeyProviders.KeyFile.ToString()))
          {
            _keyFile = WebUtility.UrlDecode((type.Substring(KeyProviders.ChallengeXC.ToString().Length)).Substring(KeyProviders.KeyFile.ToString().Length));
            Kp2aLog.Log("Added XC key file of length " + _keyFile.Length);

            KeyProviderTypes.Add(KeyProviders.ChallengeXC);
            KeyProviderTypes.Add(KeyProviders.KeyFile);
            continue;
          }
          foreach (KeyProviders providerType in Enum.GetValues(typeof(KeyProviders)))
          {
            if (type == providerType.ToString())
            {
              KeyProviderTypes.Add(providerType);
              break;
            }
          }
        }
      }
      else
      {
        Kp2aLog.Log("PasswordActivity: legacy key file mode");
        //legacy mode
        _keyFile = null;

        if (keyProviderString == KeyProviderIdOtp)
          KeyProviderTypes.Add(KeyProviders.Otp);
        else if (keyProviderString == KeyProviderIdOtpRecovery)
          KeyProviderTypes.Add(KeyProviders.OtpRecovery);
        else if (keyProviderString == KeyProviderIdChallenge)
          KeyProviderTypes.Add(KeyProviders.Challenge);
        else if (keyProviderString == KeyProviderIdChallengeRecovery)
          KeyProviderTypes.Add(KeyProviders.ChalRecovery);
        else if (keyProviderString == KeyProviderIdChallengeXC)
          KeyProviderTypes.Add(KeyProviders.ChallengeXC);
        else
        {
          KeyProviderTypes.Add(KeyProviders.KeyFile);
          _keyFile = keyProviderString;
        }



        if (KeyProviderTypes.Contains(KeyProviders.KeyFile))
        {
          //test if the filename is properly encoded. 
          try
          {
            Kp2aLog.Log("test if stored filename is ok");
            IOConnectionInfo.UnserializeFromString(_keyFile);
            Kp2aLog.Log("...ok");
          }
          catch (Exception e)
          {
            //it's not. This is probably because we're upgrading from app version <= 45
            //where the keyfile was stored plain text and not serialized
            Kp2aLog.Log("no, it's not: " + e.GetType().Name);
            var serializedKeyFile = IOConnectionInfo.SerializeToString(IOConnectionInfo.FromPath(_keyFile));
            Kp2aLog.Log("now it is!");
            _keyFile = serializedKeyFile;

          }
        }
      }
    }

    private void ClearFingerprintUnlockData()
    {
      ISharedPreferencesEditor edit = PreferenceManager.GetDefaultSharedPreferences(this).Edit();
      edit.PutString(Database.GetFingerprintPrefKey(_ioConnection), "");
      edit.PutString(Database.GetFingerprintModePrefKey(_ioConnection), FingerprintUnlockMode.Disabled.ToString());
      edit.Commit();
    }

    public void OnBiometricError(string message)
    {
      var btn = FindViewById<ImageButton>(Resource.Id.fingerprintbtn);

      btn.SetImageResource(Resource.Drawable.ic_fingerprint_error);
      btn.PostDelayed(() =>
      {
        btn.SetImageResource(Resource.Drawable.baseline_fingerprint_24);
      }, 1300);
      App.Kp2a.ShowMessage(this, message, MessageSeverity.Error);
    }

    public void OnBiometricAttemptFailed(string message)
    {
      //ignore
    }

    public void OnBiometricAuthSucceeded()
    {
      var btn = FindViewById<ImageButton>(Resource.Id.fingerprintbtn);

      btn.SetImageResource(Resource.Drawable.ic_fingerprint_success);

      try
      {
        var masterPassword = _biometricDec.DecryptStored(Database.GetFingerprintPrefKey(_ioConnection));
        //first mask the password textedit before assigning the password:
        if (_showPassword)
        {
          _showPassword = false;
          MakePasswordMaskedOrVisible();

        }
        _password = FindViewById<EditText>(Resource.Id.password_edit).Text = masterPassword;

        FindViewById<EditText>(Resource.Id.password_edit).Enabled = false; //prevent accidental modification of password

      }
      catch (Java.Security.GeneralSecurityException ex)
      {
        Kp2aLog.Log("GeneralSecurityException in DecryptStored");
        Kp2aLog.LogUnexpectedError(ex);
        HandleFingerprintKeyInvalidated();
        return;
      }

      btn.PostDelayed(() =>
      {
        //fire if everything else is ready
        if (FindViewById(Resource.Id.pass_ok).Enabled)
        {
          OnOk(true);
        }

        FindViewById<EditText>(Resource.Id.password_edit).Enabled = true;
      }, 500);

    }

    private void InitializeNavDrawerButtons()
    {
      FindViewById(Resource.Id.btn_nav_change_db).Click += (sender, args) =>
      {
        GoToFileSelectActivity();
      };

      FindViewById(Resource.Id.btn_nav_donate).Click += (sender, args) =>
      {
        Util.GotoDonateUrl(this);
      };
      FindViewById(Resource.Id.btn_nav_donate).Visibility =
          PreferenceManager.GetDefaultSharedPreferences(this)
              .GetBoolean(GetString(Resource.String.NoDonateOption_key), false)
              ? ViewStates.Gone
              : ViewStates.Visible;
      FindViewById(Resource.Id.btn_nav_about).Click += (sender, args) =>
      {
        AboutDialog dialog = new AboutDialog(this);
        dialog.Show();
      };

      FindViewById(Resource.Id.btn_nav_settings).Click += (sender, args) =>
      {
        AppSettingsActivity.Launch(this);
      };

      FindViewById(Resource.Id.nav_app_name).Click += (sender, args) =>
      {
        _appnameclickCount++;
        if (_appnameclickCount == 6)
        {
          Kp2aLog.LogUnexpectedError(new Exception("some blabla"));
          App.Kp2a.ShowMessage(this, "Once again and the app will crash.", MessageSeverity.Warning);
        }

        if (_appnameclickCount == 7)
        {
          throw new Exception("this is an easter egg crash (to test uncaught exceptions.)");
        }


      };

    }

    private void InitializeToolbar()
    {


      var collapsingToolbar = FindViewById<CollapsingToolbarLayout>(Resource.Id.collapsing_toolbar);
      collapsingToolbar.SetTitle(GetString(Resource.String.unlock_database_title));

      _drawerLayout = FindViewById<DrawerLayout>(Resource.Id.drawer_layout);
      mDrawerToggle = new ActionBarDrawerToggle(this, _drawerLayout,
          Resource.String.menu_open,
          Resource.String.menu_close);


      _drawerLayout?.SetDrawerListener(mDrawerToggle);

      SetSupportActionBar(FindViewById<Toolbar>(Resource.Id.toolbar));
      SupportActionBar.SetDisplayHomeAsUpEnabled(true);
      SupportActionBar.SetHomeButtonEnabled(true);
      mDrawerToggle.SyncState();

    }

    public override void OnBackPressed()
    {
      if (_drawerLayout != null)
      {
        if (_drawerLayout.IsDrawerOpen((int)GravityFlags.Start))
        {
          _drawerLayout.CloseDrawer((int)GravityFlags.Start);
          return;
        }
      }
      base.OnBackPressed();
    }

    private void InitializeOtpSecretSpinner()
    {
      Spinner spinner = FindViewById<Spinner>(Resource.Id.otpsecret_format_spinner);
      ArrayAdapter<String> spinnerArrayAdapter = new ArrayAdapter<String>(this, Android.Resource.Layout.SimpleSpinnerDropDownItem, EncodingUtil.Formats);
      spinner.Adapter = spinnerArrayAdapter;
    }

    private bool GetIocFromOtpIntent(Bundle savedInstanceState, Intent i)
    {
      //create called after detecting an OTP via NFC
      //this means the Activity was not on the back stack before, i.e. no database has been selected

      _ioConnection = null;

      //see if we can get a database from recent:
      if (App.Kp2a.FileDbHelper.HasRecentFiles())
      {
        ICursor filesCursor = App.Kp2a.FileDbHelper.FetchAllFiles();
        StartManagingCursor(filesCursor);
        filesCursor.MoveToFirst();
        IOConnectionInfo ioc = App.Kp2a.FileDbHelper.CursorToIoc(filesCursor);
        if (App.Kp2a.GetFileStorage(ioc).RequiresSetup(ioc) == false)
        {
          IFileStorage fileStorage = App.Kp2a.GetFileStorage(ioc);

          if (!fileStorage.RequiresCredentials(ioc))
          {
            //ok, we can use this file
            _ioConnection = ioc;
          }
        }
      }

      if (_ioConnection == null)
      {
        //We need to go to FileSelectActivity first.
        //For security reasons: discard the OTP (otherwise the user might not select a database now and forget 
        //about the OTP, but it would still be stored in the Intents and later be passed to PasswordActivity again.

        App.Kp2a.ShowMessage(this, GetString(Resource.String.otp_discarded_because_no_db), MessageSeverity.Warning);
        GoToFileSelectActivity();
        return false;
      }

      //assume user wants to use OTP (for static password, they need to open KP2A first and select the key provider type, then see OnNewIntent)
      KeyProviderTypes.Clear();
      KeyProviderTypes.Add(KeyProviders.Otp);

      if (savedInstanceState == null) //only when not re-creating
      {
        //remember the OTP for later use
        _pendingOtps.Add(i.GetStringExtra(Intents.OtpExtraKey));
        i.RemoveExtra(Intents.OtpExtraKey);
      }
      return true;
    }


    private void InitializeBottomBarButtons()
    {
      Button confirmButton = (Button)FindViewById(Resource.Id.pass_ok);
      confirmButton.Click += (sender, e) =>
          {
            OnOk();
          };

      var changeDbButton = FindViewById<Button>(Resource.Id.change_db);
      string label = changeDbButton.Text;
      if (label.EndsWith("\u2026"))
        changeDbButton.Text = label.Substring(0, label.Length - 1);
      changeDbButton.Click += (sender, args) => GoToFileSelectActivity();

      Util.MoveBottomBarButtons(Resource.Id.change_db, Resource.Id.pass_ok, Resource.Id.bottom_bar, this);
      Util.InsetListener.ForBottomElement(FindViewById(Resource.Id.bottom_bar)).Apply();
      Util.InsetListener.ForTopElement(FindViewById(Resource.Id.appbar)).Apply();
    }

    private void OnOk(bool usedFingerprintUnlock = false)
    {
      UsedFingerprintUnlock = usedFingerprintUnlock;
      App.Kp2a.GetFileStorage(_ioConnection)
         .PrepareFileUsage(new FileStorageSetupInitiatorActivity(this, OnActivityResult, null), _ioConnection,
                           RequestCodePrepareDbFile, false);
    }


    private void InitializeTogglePasswordButton()
    {
      ImageButton btnTogglePassword = (ImageButton)FindViewById(Resource.Id.toggle_password);
      btnTogglePassword.Click += (sender, e) =>
          {
            _showPassword = !_showPassword;
            MakePasswordMaskedOrVisible();
          };
      Android.Graphics.PorterDuff.Mode mMode = Android.Graphics.PorterDuff.Mode.SrcAtop;
      Color color = new Color(224, 224, 224);
      btnTogglePassword.SetColorFilter(color, mMode);
    }

    private void InitializeKeyfileBrowseButton()
    {
      var browseButton = (Button)FindViewById(Resource.Id.btn_change_location);
      browseButton.Click += (sender, evt) =>
          {
            Intent intent = new Intent(this, typeof(SelectStorageLocationActivity));
            intent.PutExtra(FileStorageSelectionActivity.AllowThirdPartyAppGet, true);
            intent.PutExtra(FileStorageSelectionActivity.AllowThirdPartyAppSend, false);
            intent.PutExtra(FileStorageSetupDefs.ExtraIsForSave, false);
            StartActivityForResult(intent, RequestCodeSelectKeyfile);
          };
    }

    private void InitializePasswordModeSpinner()
    {
      Spinner passwordModeSpinner = FindViewById<Spinner>(Resource.Id.password_mode_spinner);
      if (passwordModeSpinner != null)
      {
        UpdateKeyProviderUiState();

        int spinnerPos = 0;
        if (KeyProviderTypes.Contains(KeyProviders.KeyFile))
        {
          if (KeyProviderTypes.Contains(KeyProviders.ChallengeXC))
            spinnerPos = 7;
          else spinnerPos = 1;
        }
        else if (KeyProviderTypes.Contains(KeyProviders.Otp))
          spinnerPos = 2;
        else if (KeyProviderTypes.Contains(KeyProviders.OtpRecovery))
          spinnerPos = 3;
        else if (KeyProviderTypes.Contains(KeyProviders.Challenge))
          spinnerPos = 4;
        else if (KeyProviderTypes.Contains(KeyProviders.ChalRecovery))
          spinnerPos = 5;
        else if (KeyProviderTypes.Contains(KeyProviders.ChallengeXC))
          spinnerPos = 6;

        passwordModeSpinner.SetSelection(spinnerPos);
        passwordModeSpinner.ItemSelected += (sender, args) =>
        {
          KeyProviderTypes.Clear();
          _keyFile = null;
          Kp2aLog.Log("PasswordModeSpinner item selected: " + args.Position);
          switch (args.Position)
          {
            case 0:
              break;
            case 1:
              //don't set to "" to prevent losing the filename. (ItemSelected is also called during recreation!)
              Kp2aLog.Log("key file length before: " + _keyFile?.Length);
              _keyFile = (FindViewById(Resource.Id.label_keyfilename).Tag ?? "").ToString();
              Kp2aLog.Log("key file length after: " + _keyFile?.Length);
              KeyProviderTypes.Add(KeyProviders.KeyFile);
              break;
            case 2:
              KeyProviderTypes.Add(KeyProviders.Otp);
              break;
            case 3:
              KeyProviderTypes.Add(KeyProviders.OtpRecovery);
              break;
            case 4:
              KeyProviderTypes.Add(KeyProviders.Challenge);
              break;
            case 5:
              KeyProviderTypes.Add(KeyProviders.ChalRecovery);
              break;
            case 6:
              KeyProviderTypes.Add(KeyProviders.ChallengeXC);
              break;
            case 7:
              //don't set to "" to prevent losing the filename. (ItemSelected is also called during recreation!)
              Kp2aLog.Log("key file length before: " + _keyFile?.Length);
              _keyFile = (FindViewById(Resource.Id.label_keyfilename).Tag ?? "").ToString();
              Kp2aLog.Log("key file length after: " + _keyFile?.Length);
              KeyProviderTypes.Add(KeyProviders.ChallengeXC);
              KeyProviderTypes.Add(KeyProviders.KeyFile);
              break;
            default:
              throw new Exception("Unexpected position " + args.Position + " / " +
                                          ((ICursor)((AdapterView)sender).GetItemAtPosition(args.Position)).GetString(1));
          }
          UpdateKeyProviderUiState();
        };
        FindViewById(Resource.Id.init_otp).Click += (sender, args) =>
            {
              App.Kp2a.GetOtpAuxFileStorage(_ioConnection)
                          .PrepareFileUsage(new FileStorageSetupInitiatorActivity(this, OnActivityResult, null), _ioConnection,
                                              RequestCodePrepareOtpAuxFile, false);
            };
      }
    }

    private void RestoreState(Bundle savedInstanceState)
    {
      if (savedInstanceState != null)
      {
        Kp2aLog.Log("PasswordActivity: Restoring state from savedInstanceState");
        _showPassword = savedInstanceState.GetBoolean(ShowpasswordKey, false);
        MakePasswordMaskedOrVisible();

        if (!string.IsNullOrEmpty(savedInstanceState.GetString(KeyFileOrProviderKey)))
          Kp2aLog.Log("No key provider found");
        else
          Kp2aLog.Log("Key provider found");
        SetKeyProviderFromString(savedInstanceState.GetString(KeyFileOrProviderKey));
        _password = FindViewById<EditText>(Resource.Id.password_edit).Text = savedInstanceState.GetString(PasswordKey);

        _pendingOtps = new List<string>(savedInstanceState.GetStringArrayList(PendingOtpsKey));

        string otpInfoString = savedInstanceState.GetString(OtpInfoKey);
        if (otpInfoString != null)
        {

          XmlSerializer xs = new XmlSerializer(typeof(OtpInfo));

          XmlReaderSettings settings = new XmlReaderSettings() { XmlResolver = null, DtdProcessing = DtdProcessing.Ignore };
          var reader = XmlReader.Create(new StringReader(otpInfoString), settings);

          _otpInfo = (OtpInfo)xs.Deserialize(reader);

          var enteredOtps = savedInstanceState.GetStringArrayList(EnteredOtpsKey);

          ShowOtpEntry(enteredOtps);
        }

        UpdateKeyProviderUiState();

      }
    }

    private void UpdateOkButtonState()
    {
      bool enabled = true;
      if (KeyProviderTypes.Contains(KeyProviders.KeyFile))
        enabled &= _keyFile != "" || _password != "";

      if (KeyProviderTypes.Contains(KeyProviders.Otp))
      {
        if (_otpInfo == null)
          enabled = false;
        else
        {
          int c = 0;
          foreach (int otpId in _otpTextViewIds)
          {
            c++;
            var otpTextView = FindViewById<EditText>(otpId);
            if ((c <= _otpInfo.OtpsRequired) && (otpTextView.Text == ""))
            {
              enabled = false;
              break;
            }
          }
        }
      }


      if (KeyProviderTypes.Contains(KeyProviders.OtpRecovery) || KeyProviderTypes.Contains(KeyProviders.ChalRecovery))
      {
        enabled &= FindViewById<EditText>(Resource.Id.pass_otpsecret).Text != "";
      }

      if (KeyProviderTypes.Contains(KeyProviders.Challenge))
        enabled &= _challengeSecret != null;

      FindViewById(Resource.Id.pass_ok).Enabled = enabled;

    }

    private void UpdateKeyProviderUiState()
    {
      FindViewById(Resource.Id.keyfileLine).Visibility = KeyProviderHasKeyFile()
                                                             ? ViewStates.Visible
                                                             : ViewStates.Gone;
      if (KeyProviderHasKeyFile())
      {
        UpdateKeyfileIocView();
      }

      FindViewById(Resource.Id.otpView).Visibility = KeyProviderTypes.Contains(KeyProviders.Otp)
                                                         ? ViewStates.Visible
                                                         : ViewStates.Gone;

      FindViewById(Resource.Id.otpSecretLine).Visibility = (KeyProviderTypes.Contains(KeyProviders.OtpRecovery) || KeyProviderTypes.Contains(KeyProviders.ChalRecovery))
                                                         ? ViewStates.Visible
                                                         : ViewStates.Gone;
      if (KeyProviderTypes.Contains(KeyProviders.Otp))
      {
        FindViewById(Resource.Id.otps_pending).Visibility = _pendingOtps.Count > 0 ? ViewStates.Visible : ViewStates.Gone;
      }

      if (KeyProviderTypes.Contains(KeyProviders.Challenge))
      {
        FindViewById(Resource.Id.otpView).Visibility = ViewStates.Visible;
        FindViewById(Resource.Id.otps_pending).Visibility = ViewStates.Gone;
      }
      UpdateOkButtonState();
    }

    private bool KeyProviderHasKeyFile()
    {
      return KeyProviderTypes.Contains(KeyProviders.KeyFile);
    }

    private void PerformLoadDatabase()
    {
      if (_performingLoad)
        return;
      CurrentlyWaitingKey = null;
      _performingLoad = true;
      //put loading into background thread to allow loading the key file (potentially over network)
      new SimpleLoadingDialog(this, GetString(Resource.String.loading),
                              true, () =>
                                  {
                                    CompositeKey compositeKey;
                                    string errorMessage;
                                    if (!CreateCompositeKey(out compositeKey, out errorMessage)) return (() =>
                                              {
                                                App.Kp2a.ShowMessage(this, errorMessage, MessageSeverity.Warning);
                                                _performingLoad = false;
                                              });
                                    return () => { PerformLoadDatabaseWithCompositeKey(compositeKey); };
                                  }).Execute();


    }

    private void PerformLoadDatabaseWithCompositeKey(CompositeKey compositeKey)
    {
      CheckBox cbQuickUnlock = (CheckBox)FindViewById(Resource.Id.enable_quickunlock);
      if (cbQuickUnlock == null)
        throw new NullPointerException("cpQuickUnlock");
      App.Kp2a.SetQuickUnlockEnabled(cbQuickUnlock.Checked);
      App.Kp2a.ScreenLockWasEnabledWhenOpeningDatabase =
          (((KeyguardManager)GetSystemService(Context.KeyguardService)!)!).IsDeviceSecure;
      App.Kp2a.QuickUnlockBlockedWhenDeviceNotSecureWhenOpeningDatabase = PreferenceManager.GetDefaultSharedPreferences(this)
          .GetBoolean(GetString(Resource.String.QuickUnlockBlockedWhenDeviceNotSecure_key), true);

      if ((_loadDbFileTask != null) && (App.Kp2a.OfflineMode != _loadDbTaskOffline))
      {
        if (App.Kp2a == null)
          throw new NullPointerException("App.Kp2a");
        //keep the loading result if we loaded in online-mode (now offline) and the task is completed
        if (!App.Kp2a.OfflineMode || !_loadDbFileTask.IsCompleted)
        {
          //discard the pre-loading task
          _loadDbFileTask = null;
        }

      }

      //avoid password being visible while loading:
      _showPassword = false;
      try
      {
        MakePasswordMaskedOrVisible();

        Handler handler = new Handler();
        OnOperationFinishedHandler onOperationFinishedHandler = new AfterLoad(handler, this, _ioConnection);
        LoadDb loadOperation = (KeyProviderTypes.Contains(KeyProviders.Otp))
            ? new SaveOtpAuxFileAndLoadDb(App.Kp2a, _ioConnection, _loadDbFileTask, compositeKey, GetKeyProviderString(),
                onOperationFinishedHandler, this, true, _makeCurrent)
            : new LoadDb(App.Kp2a, _ioConnection, _loadDbFileTask, compositeKey, GetKeyProviderString(), onOperationFinishedHandler, true, _makeCurrent);
        _loadDbFileTask = null; // prevent accidental re-use

        _lastLoadOperation = loadOperation;


        //Don't use BlockingOperationStarter as that would cancel running operations.
        //This is bad when used with AutoOpen: we might get here when one database has loaded and is now synced in the background.
        //We don't want to cancel this.
        OperationRunner.Instance.Run(App.Kp2a, loadOperation, true);

      }
      catch (Exception e)
      {
        Kp2aLog.LogUnexpectedError(new Exception("cannot load database: " + e + ", c: " + (compositeKey != null) + (_ioConnection != null) + (_keyFile != null), e));
        throw;
      }

    }

    private bool CreateCompositeKey(out CompositeKey compositeKey, out string errorMessage)
    {
      errorMessage = null;
      //no need to check for validity of password because if this method is called, the Ok button was enabled (i.e. there was a valid password)
      compositeKey = new CompositeKey();
      compositeKey.AddUserKey(new KcpPassword(_password));
      if (KeyProviderTypes.Contains(KeyProviders.KeyFile))
      {
        try
        {
          if (_keyFile == "")
            throw new System.IO.FileNotFoundException();
          var ioc = IOConnectionInfo.UnserializeFromString(_keyFile);
          using (var stream = App.Kp2a.GetFileStorage(ioc).OpenFileForRead(ioc))
          {
            byte[] keyfileData = Util.StreamToMemoryStream(stream).ToArray();
            compositeKey.AddUserKey(new KcpKeyFile(keyfileData, ioc, true));
          }
        }
        catch (System.IO.FileNotFoundException e)
        {
          Kp2aLog.Log(e.ToString());
          errorMessage = App.Kp2a.GetResourceString(UiStringKey.keyfile_does_not_exist);
          return false;
        }
        catch (Exception e)
        {
          Kp2aLog.LogUnexpectedError(e);
          errorMessage = Util.GetErrorMessage(e);
          return false;
        }
      }
      if (KeyProviderTypes.Contains(KeyProviders.Otp))
      {
        try
        {
          var lOtps = GetOtpsFromUi();
          Kp2aLog.Log("received " + lOtps.Count + " otps.");
          OathHotpKeyProv.CreateOtpSecret(lOtps, _otpInfo);
        }
        catch (Exception e)
        {
          Kp2aLog.LogUnexpectedError(e);
          errorMessage = GetString(Resource.String.OtpKeyError);

          return false;
        }
        compositeKey.AddUserKey(new KcpCustomKey(OathHotpKeyProv.Name, _otpInfo.Secret, true));
      }
      if ((KeyProviderTypes.Contains(KeyProviders.OtpRecovery)) || (KeyProviderTypes.Contains(KeyProviders.ChalRecovery)))
      {
        Spinner stpDataFmtSpinner = FindViewById<Spinner>(Resource.Id.otpsecret_format_spinner);
        EditText secretEdit = FindViewById<EditText>(Resource.Id.pass_otpsecret);

        byte[] pbSecret = EncodingUtil.ParseKey(secretEdit.Text, (OtpDataFmt)stpDataFmtSpinner.SelectedItemPosition);
        if (pbSecret != null)
        {
          compositeKey.AddUserKey(new KcpCustomKey(OathHotpKeyProv.Name, pbSecret, true));
        }
        else
        {
          errorMessage = GetString(Resource.String.CouldntParseOtpSecret);
          return false;
        }
      }
      if (KeyProviderTypes.Contains(KeyProviders.Challenge))
      {
        compositeKey.AddUserKey(new KcpCustomKey(KeeChallengeProv.Name, _challengeSecret, true));
      }
      if (KeyProviderTypes.Contains(KeyProviders.ChallengeXC))
      {
        CurrentlyWaitingKey = new ChallengeXCKey(this, RequestCodeChallengeYubikey);
        compositeKey.AddUserKey(CurrentlyWaitingKey);
      }
      return true;
    }

    private List<string> GetOtpsFromUi()
    {
      List<string> lOtps = new List<string>();
      foreach (int otpId in _otpTextViewIds)
      {
        string otpText = FindViewById<EditText>(otpId).Text;
        if (!String.IsNullOrEmpty(otpText))
          lOtps.Add(otpText);
      }
      return lOtps;
    }


    private void MakePasswordMaskedOrVisible()
    {
      EditText password = (EditText)FindViewById(Resource.Id.password_edit);
      int selStart = password.SelectionStart, selEnd = password.SelectionEnd;
      if (_showPassword)
      {
        password.InputType = InputTypes.ClassText | InputTypes.TextVariationVisiblePassword;
        _passwordFont.ApplyTo(password);
      }
      else
      {
        password.InputType = InputTypes.ClassText | InputTypes.TextVariationPassword;
      }
      password.SetSelection(selStart, selEnd);
    }

    protected override void OnPause()
    {
      _biometricDec?.StopListening();
      _lastOnPauseTime = DateTime.Now;

      base.OnPause();
    }

    private bool hasRequestedKeyboardActivation = false;
    private LoadDb _lastLoadOperation;

    protected override void OnStart()
    {
      base.OnStart();
      _starting = true;

      if (PreferenceManager.GetDefaultSharedPreferences(this)
          .GetBoolean(GetString(Resource.String.UseKp2aKeyboardInKp2a_key), false)
          && !hasRequestedKeyboardActivation)
      {
        hasRequestedKeyboardActivation = true;
        CopyToClipboardService.ActivateKeyboard(this);
      }

      DonateReminder.ShowDonateReminderIfAppropriate(this);


      if (compositeKeyForImmediateLoad == null && !fingerprintInitialized)
      {
        fingerprintInitialized = InitFingerprintUnlock();
      }

    }

    private MemoryStream PreloadDbFile()
    {
      Kp2aLog.Log("Pre-loading database file starting");
      var fileStorage = App.Kp2a.GetFileStorage(_ioConnection);
      var stream = fileStorage.OpenFileForRead(_ioConnection);

      var memoryStream = Util.StreamToMemoryStream(stream);

      Kp2aLog.Log("Pre-loading database file completed");

      return memoryStream;
    }

    protected override void OnSaveInstanceState(Bundle outState)
    {
      base.OnSaveInstanceState(outState);
      outState.PutBoolean(ShowpasswordKey, _showPassword);

      outState.PutString(KeyFileOrProviderKey, GetKeyProviderString());
      outState.PutString(PasswordKey, _password);
      outState.PutStringArrayList(PendingOtpsKey, _pendingOtps);
      if (_otpInfo != null)
      {
        outState.PutStringArrayList(EnteredOtpsKey, GetOtpsFromUi());

        var sw = new StringWriter();

        var xws = OtpInfo.XmlWriterSettings();

        XmlWriter xw = XmlWriter.Create(sw, xws);

        XmlSerializer xs = new XmlSerializer(typeof(OtpInfo));
        xs.Serialize(xw, _otpInfo);

        xw.Close();

        outState.PutString(OtpInfoKey, sw.ToString());
      }

    }

    protected override void OnNewIntent(Intent intent)
    {
      base.OnNewIntent(intent);

      //this method is called from the NfcOtpActivity's startActivity() if the activity is already running
      //note: it's not called in other cases because OnNewIntent requires the activity to be on top already 
      //which is never the case when started from another activity (in the same task).
      //NfcOtpActivity sets the ClearTop flag to get OnNewIntent called.
      if (intent != null)
      {
        if (intent.HasExtra(Intents.OtpExtraKey))
        {
          string otp = intent.GetStringExtra(Intents.OtpExtraKey);
          _keepPasswordInOnResume = true;
          if (KeyProviderTypes.Contains(KeyProviders.Otp))
          {

            if (_otpInfo == null)
            {
              //Entering OTPs not yet initialized:
              _pendingOtps.Add(otp);
              UpdateKeyProviderUiState();
            }
            else
            {
              //Entering OTPs is initialized. Write OTP into first empty field:
              bool foundEmptyField = false;
              foreach (int otpId in _otpTextViewIds)
              {
                EditText otpEdit = FindViewById<EditText>(otpId);
                if ((otpEdit.Visibility == ViewStates.Visible) && String.IsNullOrEmpty(otpEdit.Text))
                {
                  otpEdit.Text = otp;
                  foundEmptyField = true;
                  break;
                }
              }
              //did we find a field?
              if (!foundEmptyField)
              {
                App.Kp2a.ShowMessage(this, GetString(Resource.String.otp_discarded_no_space), MessageSeverity.Error);
              }
            }

            Spinner passwordModeSpinner = FindViewById<Spinner>(Resource.Id.password_mode_spinner);
            if (passwordModeSpinner.SelectedItemPosition != (int)KeyProviders.Otp)
            {
              passwordModeSpinner.SetSelection((int)KeyProviders.Otp);
            }
          }
          else
          {
            //assume the key should be used as static password
            FindViewById<EditText>(Resource.Id.password_edit).Text += otp;
          }
        }
        else
        {
          // if the activity is launched twice and the first initialization hasn't even finished, we cannot 
          // reset the state and re-initialize the activity.
          // This can happen with autofill in some cases (#2869)
          if (_resumeCompleted)
          {
            ResetState();
            GetIocFromLaunchIntent(intent);
            InitializeAfterSetIoc();
            OnStart();
          }

        }
      }

    }

    private void ResetState()
    {
      _makeCurrent = false;
      _loadDbFileTask = null;
      _loadDbTaskOffline = false;

      _showPassword = false;
      MakePasswordMaskedOrVisible();
      _killOnDestroy = false;
      _password = "";
      _pendingOtps.Clear();
      KeyProviderTypes.Clear();
      _rememberKeyfile = false;
      _starting = false;
      _otpInfo = null;
      _otpAuxIoc = null;
      _chalInfo = null;
      _challengeSecret = null;
      _challengeProv = null;
      _performingLoad = false;
      _keepPasswordInOnResume = false;
      _lastOnPauseTime = DateTime.MinValue;
      mDrawerTitle = "";
      _measureArgs = null;
      _biometricDec = null;
      _appnameclickCount = 0;
      InvalidCompositeKeyCount = 0;
      LoadingErrorCount = 0;
      fingerprintInitialized = false;
      UsedFingerprintUnlock = false;
    }

    protected override View? SnackbarAnchorView => FindViewById(Resource.Id.main_content);

    protected override void OnResume()
    {
      base.OnResume();

      _activityDesign.ReapplyTheme();

      Kp2aLog.Log("starting: " + _starting + ", Finishing: " + IsFinishing + ", _performingLoad: " +
                  _performingLoad);

      CheckBox cbOfflineMode = (CheckBox)FindViewById(Resource.Id.work_offline);
      App.Kp2a.OfflineMode =
          cbOfflineMode.Checked =
              App.Kp2a
                  .OfflineModePreference; //this won't overwrite new user settings because every change is directly saved in settings

      CheckBox cbSyncInBackground = (CheckBox)FindViewById(Resource.Id.sync_in_background)!;
      cbSyncInBackground.Checked = App.Kp2a.SyncInBackgroundPreference;
      UpdateInternalCacheCheckboxesVisibility();




      View killButton = FindViewById(Resource.Id.kill_app);
      if (PreferenceManager.GetDefaultSharedPreferences(this)
          .GetBoolean(GetString(Resource.String.show_kill_app_key), false))
      {
        killButton.Click += (sender, args) =>
        {
          _killOnDestroy = true;
          SetResult(Result.Canceled);
          Finish();

        };
        killButton.Visibility = ViewStates.Visible;

      }
      else
      {
        killButton.Visibility = ViewStates.Gone;
      }

      TryGetOtpFromClipboard();

      if (!_keepPasswordInOnResume)
      {
        if (
            _lastOnPauseTime <
            DateTime.Now -
            TimeSpan.FromSeconds(
                5) //only clear when user left the app for more than 5 seconds (allows to use Yubiclip, also allows to switch shortly to another app)
            &&
            PreferenceManager.GetDefaultSharedPreferences(this)
                .GetBoolean(GetString(Resource.String.ClearPasswordOnLeave_key), true))
        {
          ClearEnteredPassword();
        }

      }



      _keepPasswordInOnResume = false;

      MakePasswordMaskedOrVisible();

      UpdateOkButtonState();

      if (KeyProviderTypes.Contains(KeyProviders.Challenge))
      {
        FindViewById(Resource.Id.otpInitView).Visibility =
            _challengeSecret == null ? ViewStates.Visible : ViewStates.Gone;
      }

      //use !IsFinishing to make sure we're not starting another activity when we're already finishing (e.g. due to TaskComplete in OnActivityResult)
      //use !performingLoad to make sure we're not already loading the database (after ActivityResult from File-Prepare-Activity; this would cause _loadDbFileTask to exist when we reload later!)
      if (!IsFinishing && !_performingLoad)
      {


        // OnResume is run every time the activity comes to the foreground. This code should only run when the activity is started (OnStart), but must
        // be run in OnResume rather than OnStart so that it always occurrs after OnActivityResult (when re-creating a killed activity, OnStart occurs before OnActivityResult)
        if (_starting)
        {

          _starting = false;

          //database not yet loaded.

          //check if pre-loading is enabled but wasn't started yet:
          if (_loadDbFileTask == null &&
              _prefs.GetBoolean(GetString(Resource.String.PreloadDatabaseEnabled_key), true))
          {
            // Create task to kick off file loading while the user enters the password
            _loadDbFileTask = Task.Factory.StartNew(PreloadDbFile);
            _loadDbTaskOffline = App.Kp2a.OfflineMode;
          }
        }

      }

      if (compositeKeyForImmediateLoad != null)
      {
        //reload the database (without most other stuff performed in PerformLoadDatabase.
        // We're assuming that the db file (and if appropriate also the key file) are still available 
        // and there's no need to re-init the file storage. if it is, loading will fail and the user has 
        // to retry with typing the full password, but that's intended to avoid showing the password to a 
        // a potentially unauthorized user (feature request https://keepass2android.codeplex.com/workitem/274)
        Handler handler = new Handler();
        OnOperationFinishedHandler onOperationFinishedHandler = new AfterLoad(handler, this, _ioConnection);
        _performingLoad = true;
        LoadDb loadOperation = new LoadDb(App.Kp2a, _ioConnection, _loadDbFileTask, compositeKeyForImmediateLoad, GetKeyProviderString(),
            onOperationFinishedHandler, false, _makeCurrent);
        _loadDbFileTask = null; // prevent accidental re-use
        _lastLoadOperation = loadOperation;
        OperationRunner.Instance.Run(App.Kp2a, loadOperation, true);
        compositeKeyForImmediateLoad = null; //don't reuse or keep in memory

      }
      else
      {
        bool showKeyboard = true;


        EditText pwd = (EditText)FindViewById(Resource.Id.password_edit);
        pwd.PostDelayed(() =>
        {
          InputMethodManager keyboard = (InputMethodManager)GetSystemService(InputMethodService);
          if (showKeyboard)
          {
            pwd.RequestFocus();
            keyboard.ShowSoftInput(pwd, 0);
          }
          else
            keyboard.HideSoftInputFromWindow(pwd.WindowToken, HideSoftInputFlags.ImplicitOnly);
        }, 50);
      }

      _resumeCompleted = true;
    }

    private void TryGetOtpFromClipboard()
    {
      if (_otpInfo != null)
      {
        if ((int)Build.VERSION.SdkInt >= 26)
        {
          Android.Content.ClipboardManager clipboardManager = (ClipboardManager)GetSystemService(Context.ClipboardService);
          if (clipboardManager?.PrimaryClip?.Description == null || (clipboardManager.PrimaryClip.Description.Timestamp <
              Java.Lang.JavaSystem.CurrentTimeMillis() - 5000))
            return; //data older than 5 seconds
        }
        string clipboardContent = Util.GetClipboard(this);
        if (clipboardContent == null || (_otpInfo.OtpLength != clipboardContent.Length))
        {
          return;
        }
        foreach (char c in clipboardContent)
        {
          if (c < '0' || c > '9')
            return;
        }
        string otp = clipboardContent;

        EditText lastNonEmptyOtpEdit = null;

        foreach (int otpId in _otpTextViewIds)
        {
          EditText otpEdit = FindViewById<EditText>(otpId);
          if (otpEdit?.Visibility == ViewStates.Visible)
          {
            if (string.IsNullOrEmpty(otpEdit.Text))
            {
              if ((lastNonEmptyOtpEdit != null) && (lastNonEmptyOtpEdit.Text == otp))
                return; //otp was already set.

              //otp ok. use it:
              otpEdit.Text = otp;
              break;
            }

            lastNonEmptyOtpEdit = otpEdit;

          }

        }

      }
    }

    private bool InitFingerprintUnlock()
    {
      var btn = FindViewById<ImageButton>(Resource.Id.fingerprintbtn);
      try
      {
        FingerprintUnlockMode um;
        Enum.TryParse(_prefs.GetString(Database.GetFingerprintModePrefKey(_ioConnection), ""), out um);
        btn.Visibility = (um == FingerprintUnlockMode.FullUnlock) ? ViewStates.Visible : ViewStates.Gone;

        if (um != FingerprintUnlockMode.FullUnlock)
        {
          return false;
        }

        BiometricModule fpModule = new BiometricModule(this);
        _biometricDec = new BiometricDecryption(fpModule, Database.GetFingerprintPrefKey(_ioConnection), this,
            Database.GetFingerprintPrefKey(_ioConnection));

        if (_biometricDec.Init())
        {
          btn.SetImageResource(Resource.Drawable.baseline_fingerprint_24);
          _biometricDec.StartListening(new BiometricAuthCallbackAdapter(this, this));
          return true;
        }
        else
        {
          Kp2aLog.Log("biometricDec.Init() failed");
          HandleFingerprintKeyInvalidated();
          return false;
        }
      }
      catch (Exception e)
      {
        //exception can happen here if the app was restored from Google Backup (including preferences) but no fingerprint data is there.
        btn.SetImageResource(Resource.Drawable.ic_fingerprint_error);
        Kp2aLog.Log("failed to init fingerprint unlock:" + e);
        string error = GetString(Resource.String.FingerprintInitFailed) + " " +
                       GetString(Resource.String.fingerprint_reenable2);

        btn.Tag = error;

        App.Kp2a.ShowMessage(this, Resource.String.fingerprint_reenable2, MessageSeverity.Error);

        _biometricDec = null;
        return false;
      }


    }

    private void HandleFingerprintKeyInvalidated()
    {
      var btn = FindViewById<ImageButton>(Resource.Id.fingerprintbtn);
      //key invalidated permanently
      btn.SetImageResource(Resource.Drawable.ic_fingerprint_error);
      btn.Tag = GetString(Resource.String.fingerprint_unlock_failed) + " " + GetString(Resource.String.fingerprint_reenable2);
      _biometricDec = null;

      ClearFingerprintUnlockData();
    }

    private void InitializeOptionCheckboxes()
    {
      CheckBox cbQuickUnlock = (CheckBox)FindViewById(Resource.Id.enable_quickunlock);
      cbQuickUnlock.Checked = _prefs.GetBoolean(GetString(Resource.String.QuickUnlockDefaultEnabled_key), true);

      CheckBox cbOfflineMode = (CheckBox)FindViewById(Resource.Id.work_offline);
      cbOfflineMode.CheckedChange += (sender, args) =>
      {
        App.Kp2a.OfflineModePreference = App.Kp2a.OfflineMode = args.IsChecked;
      };

      CheckBox cbSyncInBackground = (CheckBox)FindViewById(Resource.Id.sync_in_background);
      cbSyncInBackground.CheckedChange += (sender, args) =>
      {
        App.Kp2a.SyncInBackgroundPreference = args.IsChecked;
        UpdateInternalCacheCheckboxesVisibility();

      };

    }

    private void UpdateInternalCacheCheckboxesVisibility()
    {

      LinearLayout syncInBackgroundContainer = FindViewById<LinearLayout>(Resource.Id.sync_in_background_container)!;

      LinearLayout offlineModeContainer = FindViewById<LinearLayout>(Resource.Id.work_offline_container)!;
      var cachingFileStorage = App.Kp2a.GetFileStorage(_ioConnection) as CachingFileStorage;
      if ((cachingFileStorage != null) && cachingFileStorage.IsCached(_ioConnection))
      {
        syncInBackgroundContainer.Visibility = ViewStates.Visible;
        offlineModeContainer.Visibility =
            App.Kp2a.SyncInBackgroundPreference ? ViewStates.Gone : ViewStates.Visible;
      }
      else
      {
        syncInBackgroundContainer.Visibility = offlineModeContainer.Visibility = ViewStates.Gone;
        App.Kp2a.OfflineMode = false;
      }
    }

    private String LoadKeyProviderStringForIoc(String filename)
    {
      if (_rememberKeyfile)
      {
        string keyfile = App.Kp2a.FileDbHelper.GetKeyFileForFile(filename);
        if (String.IsNullOrEmpty(keyfile))
          return null; //signal no key file

        return keyfile;
      }
      else
      {
        return null;
      }
    }

    private void InitializeFilenameView()
    {
      string filenameToShow = _ioConnection.Path;
      try
      {
        filenameToShow = App.Kp2a.GetFileStorage(_ioConnection).GetDisplayName(_ioConnection);
      }
      catch (Exception e)
      {
        Kp2aLog.LogUnexpectedError(e);
      }
      SetEditText(Resource.Id.filename, filenameToShow);

    }

    protected override void OnDestroy()
    {
      UnregisterReceiver(_intentReceiver);
      base.OnDestroy();
      if (_killOnDestroy)
        Process.KillProcess(Process.MyPid());
    }

    /*
private void errorMessage(CharSequence text)
{
    App.Kp2a.ShowMessage(this, text,  MessageSeverity.Error);
}
*/

    private void SetEditText(int resId, String str)
    {
      TextView te = (TextView)FindViewById(resId);
      //assert(te == null);

      if (te != null)
      {
        te.Text = str;
      }
    }

    public override bool OnOptionsItemSelected(IMenuItem item)
    {
      switch (item.ItemId)
      {

        case Android.Resource.Id.Home:
          _drawerLayout.OpenDrawer(GravityCompat.Start);
          return true;
      }

      return base.OnOptionsItemSelected(item);
    }

    private void GoToFileSelectActivity()
    {
      SetResult((Result)ResultSelectOtherFile);
      Finish();
    }

    private class AfterLoad : OnOperationFinishedHandler
    {
      readonly PasswordActivity _act;
      private readonly IOConnectionInfo _ioConnection;

      public AfterLoad(Handler handler, PasswordActivity act, IOConnectionInfo ioConnection) : base(App.Kp2a, handler)
      {
        _act = act;
        _ioConnection = ioConnection;
      }


      public override void Run()
      {
        if (Success)
        {

          _act.ClearEnteredPassword();
          _act.BroadcastOpenDatabase();
          _act.InvalidCompositeKeyCount = 0;
          _act.LoadingErrorCount = 0;


          GC.Collect(); // Ensure temporary memory used while loading is collected
        }

        if (Exception != null)
        {
          _act.LoadingErrorCount++;
        }



        if ((Exception != null) && (Exception.Message == KeePassLib.Resources.KLRes.FileCorrupted))
        {
          Message = _act.GetString(Resource.String.CorruptDatabaseHelp);
        }

        if (Exception is InvalidCompositeKeyException)
        {
          _act.InvalidCompositeKeyCount++;
          if (_act.UsedFingerprintUnlock)
          {
            //disable fingerprint unlock if master password changed
            _act.ClearFingerprintUnlockData();
            _act.InitFingerprintUnlock();

            Message = _act.GetString(Resource.String.fingerprint_disabled_wrong_masterkey) + " " +
                      _act.GetString(Resource.String.fingerprint_reenable2);
          }
          else
          {
            if (_act.InvalidCompositeKeyCount > 1)
            {
              Message = _act.GetString(Resource.String.RepeatedInvalidCompositeKeyHelp);
              if (_act._prefs.GetBoolean(IoUtil.GetIocPrefKey(_ioConnection, "has_local_backup"), false))
              {
                Java.Lang.Object changeDb = _act.GetString(Resource.String.menu_change_db);
                Message += _act.GetString(Resource.String.HintLocalBackupInvalidCompositeKey, new Java.Lang.Object[] { changeDb });
              }
            }
            else
            {
              Message = _act.GetString(Resource.String.FirstInvalidCompositeKeyError);
            }
          }


        }
        else if (_act.LoadingErrorCount > 1)
        {
          if (_act._prefs.GetBoolean(IoUtil.GetIocPrefKey(_ioConnection, "has_local_backup"), false))
          {
            Object changeDb = _act.GetString(Resource.String.menu_change_db);
            Message += _act.GetString(Resource.String.HintLocalBackupOtherError, changeDb);
          }

        }



        if ((Message != null) && (Message.Length > 150)) //show long messages as dialog
        {
          new MaterialAlertDialogBuilder(_act).SetMessage(Message)
                                       .SetPositiveButton(Android.Resource.String.Ok,
                                                          (sender, args) =>
                                                              {
                                                                ((Dialog)sender).Dismiss();
                                                                if (Success)
                                                                {
                                                                  _act.LaunchNextActivity();
                                                                }
                                                              })
                                      .SetCancelable(false)
                                      .Show();

        }
        else
        {
          MessageSeverity severity = Success ? MessageSeverity.Info : MessageSeverity.Error;
          App.Kp2a.ShowMessage(_act, Message, severity);
          if (Success)
          {
            _act.LaunchNextActivity();
          }

        }

        //re-init fingerprint unlock in case something went wrong with opening the database 
        if (!Success)
          _act.InitFingerprintUnlock();

        _act._lastLoadOperation = null;
        _act._performingLoad = false;

      }
    }


    private void BroadcastOpenDatabase()
    {
      App.Kp2a.BroadcastDatabaseAction(this, Strings.ActionOpenDatabase);
    }

    private void ClearEnteredPassword()
    {
      SetEditText(Resource.Id.password_edit, "");
      SetEditText(Resource.Id.pass_otpsecret, "");
      foreach (int otpId in _otpTextViewIds)
      {
        SetEditText(otpId, "");
      }
      if (_challengeSecret != null)
      {
        Array.Clear(_challengeSecret, 0, _challengeSecret.Length);
        _challengeSecret = null;
      }

    }


    class SaveOtpAuxFileAndLoadDb : LoadDb
    {
      private readonly PasswordActivity _act;


      public SaveOtpAuxFileAndLoadDb(IKp2aApp app, IOConnectionInfo ioc, Task<MemoryStream> databaseData, CompositeKey compositeKey, string keyfileOrProvider, OnOperationFinishedHandler operationFinishedHandler, PasswordActivity act, bool updateLastUsageTimestamp, bool makeCurrent) : base(app, ioc, databaseData, compositeKey, keyfileOrProvider, operationFinishedHandler, updateLastUsageTimestamp, makeCurrent)
      {
        _act = act;
      }

      public override void Run()
      {
        try
        {
          StatusLogger.UpdateMessage(UiStringKey.SavingOtpAuxFile);

          KeyProviderQueryContext ctx = new KeyProviderQueryContext(_act._ioConnection, false, false);

          if (!OathHotpKeyProv.CreateAuxFile(_act._otpInfo, ctx, _act._otpAuxIoc))
            ShowError(_act.GetString(Resource.String.ErrorUpdatingOtpAuxFile));


        }
        catch (Exception e)
        {
          Kp2aLog.LogUnexpectedError(e);

          ShowError(_act.GetString(Resource.String.ErrorUpdatingOtpAuxFile) + " " + Util.GetErrorMessage(e));
        }


        base.Run();

        if (success)
        {
          App.Kp2a.CurrentDb.OtpAuxFileIoc = _act._otpAuxIoc;
        }


      }

      private void ShowError(string message)
      {
        App.Kp2a.ShowToast(message, MessageSeverity.Error);
      }
    }
    private class PasswordActivityBroadcastReceiver : BroadcastReceiver
    {
      readonly PasswordActivity _activity;
      public PasswordActivityBroadcastReceiver(PasswordActivity activity)
      {
        _activity = activity;
      }

      public override void OnReceive(Context context, Intent intent)
      {
        switch (intent.Action)
        {
          case Intent.ActionScreenOff:
            _activity.OnScreenLocked();
            break;
        }
      }
    }

    private void OnScreenLocked()
    {
      if (_biometricDec != null)
        _biometricDec.StopListening();

    }


  }




}

